//    Copyright (C) Kherty.  All rights reserved.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Xml;
using OpenLS.Core.Serialization;

namespace OpenLS.Core
{
    public class CoreProperties : IOfficeXmlSerializable
    {
        private const string corePropertiesNamespaceUri =
            "http://schemas.openxmlformats.org/package/2006/metadata/core-properties";

        private const string dcNamespaceUri = "http://purl.org/dc/elements/1.1/";
        private const string dctermsNamespaceUri = "http://purl.org/dc/terms/";

        private static readonly XmlNamespace CorePropertiesNamespace = new XmlNamespace("cp", corePropertiesNamespaceUri,
                                                                                        false);

        private readonly Dictionary<NameAndNamespaceUri, object> _elements =
            new Dictionary<NameAndNamespaceUri, object>();

        internal int PropertyCount
        {
            get { return _elements.Count; }
        }

        public string Creator
        {
            get { return getValue<string>("creator", dcNamespaceUri); }
            set { setValue("creator", "dc", dcNamespaceUri, value); }
        }

        public string ContentStatus
        {
            get { return getValue<string>("contentStatus", corePropertiesNamespaceUri); }
            set { setValue("contentStatus", null, corePropertiesNamespaceUri, value); }
        }

        public string ContentType
        {
            get { return getValue<string>("contentType", corePropertiesNamespaceUri); }
            set { setValue("contentType", null, corePropertiesNamespaceUri, value); }
        }

        public string Title
        {
            get { return getValue<string>("title", dcNamespaceUri); }
            set { setValue("title", "dc", dcNamespaceUri, value); }
        }


        public string Category
        {
            get { return getValue<string>("category", corePropertiesNamespaceUri); }
            set { setValue("category", null, corePropertiesNamespaceUri, value); }
        }

        public DateTime? Created
        {
            get { return getStructValue<DateTime>("created", dctermsNamespaceUri); }
            set { setValue("created", "dcterms", dctermsNamespaceUri, value); }
        }

        public string Description
        {
            get { return getValue<string>("description", dcNamespaceUri); }
            set { setValue("description", "dc", dcNamespaceUri, value); }
        }

        public string Identifier
        {
            get { return getValue<string>("identifier", dcNamespaceUri); }
            set { setValue("identifier", "dc", dcNamespaceUri, value); }
        }

        public string Keywords
        {
            get { return getValue<string>("keywords", corePropertiesNamespaceUri); }
            set { setValue("keywords", null, corePropertiesNamespaceUri, value); }
        }

        public string Language
        {
            get { return getValue<string>("language", dcNamespaceUri); }
            set { setValue("language", "dc", dcNamespaceUri, value); }
        }

        public DateTime? LastPrinted
        {
            get { return getStructValue<DateTime>("lastPrinted", dctermsNamespaceUri); }
            set { setValue("lastPrinted", null, dctermsNamespaceUri, value); }
        }

        public string Modified
        {
            get { return getValue<string>("modified", corePropertiesNamespaceUri); }
            set { setValue("modified", null, corePropertiesNamespaceUri, value); }
        }

        public string Revision
        {
            get { return getValue<string>("revision", corePropertiesNamespaceUri); }
            set { setValue("revision", null, corePropertiesNamespaceUri, value); }
        }

        public string Subject
        {
            get { return getValue<string>("subject", dcNamespaceUri); }
            set { setValue("subject", "dc", dcNamespaceUri, value); }
        }

        public string Version
        {
            get { return getValue<string>("version", corePropertiesNamespaceUri); }
            set { setValue("version", null, corePropertiesNamespaceUri, value); }
        }

        public string LastModifiedBy
        {
            get { return getValue<string>("lastModifiedBy", corePropertiesNamespaceUri); }
            set { setValue("lastModifiedBy", null, corePropertiesNamespaceUri, value); }
        }

        #region IOfficeXmlSerializable Members

        void IOfficeXmlSerializable.ReadXml(ReadContext context)
        {
            using (var c = context.Read("coreProperties"))
            {
                while (c.ReaderLocalName != null)
                {
                    switch (c.ReaderLocalName)
                    {
                        case "category":
                        case "contentStatus":
                        case "title":
                        case "contentType":
                        case "created":
                        case "creator":
                        case "description":
                        case "identifier":
                        case "keywords":
                        case "language":
                        case "lastPrinted":
                        case "modified":
                        case "revision":
                        case "subject":
                        case "version":
                        case "lastModifiedBy":
                            {
                                string localName = c.ReaderLocalName;
                                var nn = new NameAndNamespaceUri(localName, c.Reader.Prefix, c.Reader.NamespaceURI);
                                string s = c.ReadElementString(localName, c.Reader.NamespaceURI);
                                switch (localName)
                                {
                                    case "lastPrinted":
                                    case "created":
                                    case "modified":
                                        _elements.Add(nn, XmlConvert.ToDateTime(s, XmlDateTimeSerializationMode.Utc));
                                        break;
                                    default:
                                        _elements.Add(nn, s);
                                        break;
                                }
                            }
                            break;
                        default:
                            {
                                string localName = c.ReaderLocalName;
                                Debug.WriteLine("warning: unexpected name" + localName);
                                var nn = new NameAndNamespaceUri(localName, c.Reader.Prefix, c.Reader.NamespaceURI);
                                string s = c.ReadElementString(localName, c.Reader.NamespaceURI);
                                _elements.Add(nn, s);
                                break;
                            }
                    }
                }
            }
        }

        void IOfficeXmlSerializable.WriteXml(WriteContext context)
        {
            using (WriteContext c = context.Write(CorePropertiesNamespace + "coreProperties"))
            {
                //\\c.DeclareNamespace("cp", defaultNamespaceUri);
                c.DeclareNamespace("dc", dcNamespaceUri);
                c.DeclareNamespace("dcterms", dctermsNamespaceUri);
                c.DeclareNamespace("dcmitype", "http://purl.org/dc/dcmitype/");
                bool usesXsi = false;
                foreach (var pair in _elements)
                {
                    if (!(pair.Value is string))
                    {
                        usesXsi = true;
                        break;
                    }
                }
                if (usesXsi)
                    c.DeclareNamespace(OfficeConstants.XsiNamespace);
                foreach (var pair in _elements)
                {
                    using (WriteContext c2 = c.Write(
                        new XmlNamespace(pair.Key.Prefix, pair.Key.NamespaceUri, false) + pair.Key.Name))
                    {
                        if (pair.Value is string)
                            c2.WriteString((string) pair.Value);
                        else
                        {
                            var dt = (DateTime) pair.Value;
                            dt = new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, dt.Second);
                            string value = XmlConvert.ToString(dt, XmlDateTimeSerializationMode.Utc);
                            if (pair.Key.NamespaceUri == dctermsNamespaceUri)
                                c2.SetString(OfficeConstants.XsiNamespace + "type", "dcterms:W3CDTF");
                            c2.WriteString(value);
                        }
                    }
                    //\\c.WriteElementString(pair.Key.Name, pair.Key.Prefix, pair.Key.NamespaceUri, value);
                }
            }
        }

        #endregion

        private T getValue<T>(string name, string namespaceUri) where T : class
        {
            var nn = new NameAndNamespaceUri(name, null, namespaceUri);
            object result;
            if (!_elements.TryGetValue(nn, out result))
                return null;
            return (T) result;
        }

        private T? getStructValue<T>(string name, string namespaceUri) where T : struct
        {
            var nn = new NameAndNamespaceUri(name, null, namespaceUri);
            object result;
            if (!_elements.TryGetValue(nn, out result))
                return null;
            return (T) result;
        }

        private void setValue(string name, string prefix, string namespaceUri, object value)
        {
            var key = new NameAndNamespaceUri(name, prefix, namespaceUri);
            if (_elements.ContainsKey(key))
                _elements.Remove(key);
            if (value != null)
                _elements.Add(key, value);
        }

        #region Nested type: NameAndNamespaceUri

        private class NameAndNamespaceUri
        {
            private readonly string _name;
            private readonly string _namespaceUri;
            private readonly string _prefix;

            public NameAndNamespaceUri(string name, string prefix, string namespaceUri)
            {
                _name = name;
                _namespaceUri = namespaceUri;
                _prefix = prefix;
            }

            public string Name
            {
                get { return _name; }
            }

            public string Prefix
            {
                get { return _prefix; }
            }

            public string NamespaceUri
            {
                get { return _namespaceUri; }
            }

            public override int GetHashCode()
            {
                return _name.GetHashCode() + _namespaceUri.GetHashCode();
            }

            public override bool Equals(object obj)
            {
                return Equals(obj as NameAndNamespaceUri);
            }

            public bool Equals(NameAndNamespaceUri other)
            {
                if (other == this)
                    return true;
                if (other == null)
                    return false;
                return _name == other._name && _namespaceUri == other._namespaceUri;
            }
        }

        #endregion
    }
}